using System;
using System.ComponentModel;
using System.Data;
using System.Text;
using System.Xml.Serialization;
using SubSonic.Utilities;
using System.Web.UI.WebControls;

namespace SubSonic.Enterprise
{
    /// <summary>
    /// Base class for persisting objects. Follows the "Active Record Design Pattern".
    /// You can read more on this pattern at http://en.wikipedia.org/wiki/Active_Record
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [Serializable]
    public abstract class AbstractRecord<T>: GenericRecord<T> where T : GenericRecord<T>, new()
    {                
        
        public static Query Query()
        {
            new T();
            return new Query(AbstractRecord<T>.BaseSchema);
        }

        protected void SetDefaults()
        {
            //initialize to default settings
            foreach (TableSchema.TableColumn col in AbstractRecord<T>.BaseSchema.Columns)
            {
                if (!String.IsNullOrEmpty(col.DefaultSetting)) {
                    QueryCommand cmdDefault = new QueryCommand(SqlFragment.SELECT + col.DefaultSetting, col.Table.Provider.Name);
                    SetColumnValue(col.ColumnName, DataService.ExecuteScalar(cmdDefault));
                } else {
                    SetColumnValue(col.ColumnName, Utility.GetDefaultSetting(col));

                }
            }
            Initialize();
        }

        public void LoadByParam(string columnName, object paramValue)
        {
            MarkOld();
            IDataReader rdr = null;
            try
            {
                rdr = new Query(BaseSchema).AddWhere(columnName, paramValue).ExecuteReader();
                if(rdr.Read())
                {
                    Load(rdr);
                }

                else
                {
                    //if no records, this is new still
                    MarkNew();
                }
            }
            finally
            {
                if(rdr != null)
                {
                    rdr.Close();
                }
            }
        }

        public void LoadByKey(object keyID)
        {
            MarkOld();
            IDataReader rdr = null;
            try
            {
                Query q = new Query(BaseSchema).AddWhere(BaseSchema.PrimaryKey.ColumnName, keyID);
                //CheckLogicalDelete(q);
                rdr = q.ExecuteReader();
                if(rdr.Read())
                {
                    Load(rdr);
                }
                else
                {
                    //if no records, this is new still
                    MarkNew();
                }
            }
            finally
            {
                if(rdr != null)
                {
                    rdr.Close();
                }
            }
        }

        

        #region CommandMethods

        public QueryCommand GetSelectCommand()
        {
            Query q = new Query(BaseSchema);
            q.QueryType = QueryType.Select;
            QueryCommand cmd = DataService.BuildCommand(q);
            return cmd;
        }

        #endregion

        #region Loaders

        /// <summary>
        /// Loads the object with the current reader's values. Assumes the reader is already moved to
        /// first position in recordset (aka has been "Read()")
        /// </summary>
        /// <param name="rdr">The RDR.</param>
        public void Load(IDataReader rdr)
        {
            // jeff_huntsman: we need to turn IsLoaded off while we load
            // for the OriginalValues to be setup again
            IsLoaded = false;   
            foreach(TableSchema.TableColumn col in BaseSchema.Columns)
            {
                try
                {
                    SetColumnValue(col.ColumnName, rdr[col.ColumnName]);                    
                }
                catch
                {
                    throw new Exception("Unable to set column value for " + col.ColumnName);
                }
            }
            IsLoaded = true;
            IsNew = false;
        }
                
        /// <summary>
        /// Opens the IDataReader, loads the object and closes the IDataReader. Unlike AbstractList.LoadAndCloseReader,
        /// this method does not assume that reader is open and in the first position!
        /// </summary>
        /// <param name="rdr"></param>
        public void LoadAndCloseReader(IDataReader rdr)
        {
            if(rdr.Read())
            {
                Load(rdr);
            }
            if(!rdr.IsClosed)
            {
                rdr.Close();
            }
        }

        #endregion

        #region Fetchers

        /// <summary>
        /// Returns all records for this table
        /// </summary>
        /// <returns>IDataReader</returns>
        public static IDataReader FetchAll()
        {
            //makes sure the table schema is loaded
            //new T();

            //build the query
            Query q = new Query(BaseSchema);
            CheckLogicalDelete(q);

            //load the reader
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());
            return rdr;
        }

        /// <summary>
        /// Returns all records for this table, ordered
        /// </summary>
        /// <returns>Generic Typed List</returns>
        /// <param name="orderBy">Column to order by</param>
        public static IDataReader FetchAll(OrderBy orderBy)
        {
            //makes sure the table schema is loaded
            //new T();

            //build the query
            Query q = new Query(BaseSchema);
            CheckLogicalDelete(q);
            q.OrderBy = orderBy;

            //load the reader
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());
            return rdr;
        }

        /// <summary>
        /// Returns all records for the given column/parameter, ordered by the passed in orderBy
        /// The expression for this is always "column=parameter"
        /// </summary>
        /// <param name="columnName">Name of the column to use in parmeter statement</param>
        /// <param name="oValue">Value of the column</param>
        /// <param name="oValue">Column to order by</param>
        /// <param name="orderBy">Ordering of results</param>
        /// <returns>IDataReader</returns>
        public static IDataReader FetchByParameter(string columnName, object oValue, OrderBy orderBy)
        {
            //makes sure the table schema is loaded
            //new T();
            //build the query
            Query q = new Query(BaseSchema);
            q.OrderBy = orderBy;
            q.AddWhere(columnName, oValue);
            CheckLogicalDelete(q);

            //load the reader
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());
            return rdr;
        }


        /// <summary>
        /// Returns all records for the given column/parameter
        /// The expression for this is always "column=parameter"
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="oValue"></param>
        /// <returns>IDataReader</returns>
        public static IDataReader FetchByParameter(string columnName, object oValue)
        {
            //new T();
            //build the query
            Query q = new Query(BaseSchema);
            q.AddWhere(columnName, oValue);
            CheckLogicalDelete(q);

            //load the reader
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());

            //load up the list
            return rdr;
        }

        /// <summary>
        /// Returns all records for the given column/parameter
        /// The expression for this is always "column=parameter"
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="comparison"></param>
        /// <param name="oValue"></param>
        /// <returns>IDataReader</returns>
        public static IDataReader FetchByParameter(string columnName, Comparison comparison, object oValue)
        {
            //new T();
            //build the query
            Query q = new Query(BaseSchema);
            q.AddWhere(columnName, comparison, oValue);
            CheckLogicalDelete(q);

            //load the reader
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());
            return rdr;
        }

        /// <summary>
        /// Returns all records for the given column/parameter
        /// The expression for this is always "column=parameter"
        /// </summary>
        /// <param name="columnName"></param>
        /// <param name="comparison"></param>
        /// <param name="oValue"></param>
        /// <param name="orderBy"></param>
        /// <returns>IDataReader</returns>
        public static IDataReader FetchByParameter(string columnName, Comparison comparison, object oValue, OrderBy orderBy)
        {
            //new T();
            //build the query
            Query q = new Query(BaseSchema);
            q.AddWhere(columnName, comparison, oValue);
            q.OrderBy = orderBy;
            CheckLogicalDelete(q);
            //load the reader
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());

            //load up the list
            return rdr;
        }

        /// <summary>
        /// Returns all records for the given query
        /// </summary>
        /// <returns>Generic Typed List</returns>
        /// <param name="query">Query for complex records</param>
        public static IDataReader FetchByQuery(Query query)
        {
            //makes sure the table schema is loaded
            //new T();
            CheckLogicalDelete(query);

            //load the reader
            IDataReader rdr = DataService.GetReader(query.BuildSelectCommand());
            return rdr;
        }

        /// <summary>
        /// Uses the passed-in object as a parameter set. Does not use the created/modified fields
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public static IDataReader Find(T item)
        {
            return Find(item, null);
        }

        public static IDataReader Find(T item, OrderBy orderBy)
        {
            //build the sql string and command statements at the same time
            Query q = new Query(BaseSchema);
            CheckLogicalDelete(q);

            //retrieve data from database

            foreach(TableSchema.TableColumn col in BaseSchema.Columns)
            {
                string columnName;
                object columnValue;
                columnName = col.ColumnName;
                columnValue = item.GetColumnValue<object>(columnName);
                if(!Utility.IsAuditField(columnName))
                {
                    object defaultValue = String.Empty;
                    switch(col.DataType)
                    {
                        case DbType.Boolean:
                            defaultValue = false;
                            break;
                        case DbType.Currency:
                        case DbType.Decimal:
                        case DbType.Int16:
                        case DbType.Double:
                        case DbType.Int32:
                            defaultValue = 0;
                            break;
                        case DbType.Date:
                        case DbType.DateTime:
                            defaultValue = new DateTime(1900, 1, 1);
                            break;
                        case DbType.Guid:
                            defaultValue = Guid.Empty;
                            break;
                    }
                    if(columnValue != null)
                    {
                        if(!columnValue.Equals(defaultValue))
                        {
                            q.AddWhere(columnName, columnValue);
                        }
                    }
                }
            }

            if(orderBy != null)
            {
                q.OrderBy = orderBy;
            }
            IDataReader rdr = DataService.GetReader(q.BuildSelectCommand());
            return rdr;
        }

        #endregion

        #region Utility

        /// <summary>
        /// Returns an ordered ListItemCollection for use with DropDowns, RadioButtonLists, and CheckBoxLists
        /// Note: This method assumes that the column in the second position is the text value column
        /// </summary>
        /// <returns></returns>
        public static ListItemCollection GetListItems()
        {
            //get the textColumn based on position
            //which should be the second column of the table
            //T item = new T();
            string textColumn = BaseSchema.Columns[1].ColumnName;
            return GetListItems(textColumn);
        }

        /// <summary>
        /// Returns an ordered ListItemCollection for use with DropDowns, RadioButtonLists, and CheckBoxLists
        /// </summary>
        /// <param name="textColumn">The name of the column which should be used as the text value column</param>
        /// <returns></returns>
        public static ListItemCollection GetListItems(string textColumn)
        {
            //T item = new T();
            ListItemCollection list = new ListItemCollection();
            string pkCol = BaseSchema.PrimaryKey.ColumnName;
            string textCol = BaseSchema.GetColumn(textColumn).ColumnName;

            //run a query retrieving the two columns
            Query q = new Query(BaseSchema);
            q.SelectList = pkCol + "," + textCol;
            q.OrderBy = OrderBy.Asc(textCol);
            IDataReader rdr = q.ExecuteReader();

            while(rdr.Read())
            {
                ListItem listItem = new ListItem(rdr[1].ToString(), rdr[0].ToString());
                list.Add(listItem);
            }
            return list;
        }

        /// <summary>
        /// If this object has a logical delete column, this method will append in the required parameter to avoid returning 
        /// deleted records
        /// </summary>
        /// <param name="q"></param>
        internal static void CheckLogicalDelete(Query q)
        {
            //check the columns and see if there's a "deleted" or "isDeleted"
            q.CheckLogicalDelete();
        }
                
        #endregion

        
    }

    [AttributeUsage(AttributeTargets.Property)]
    public class HiddenForDataBindingAttribute : Attribute
    {
        private bool _isHidden;
        public HiddenForDataBindingAttribute()
        {
            _isHidden = false;
        }

        public HiddenForDataBindingAttribute(bool isHidden)
        {
            _isHidden = isHidden;
        }

        public bool IsHidden
        {
            get { return _isHidden; }
        }
    }
}
